#!/bin/sh
###########################################################################
##                                                                       ##
##                  Language Technologies Institute                      ##
##                     Carnegie Mellon University                        ##
##                      Copyright (c) 2002-2017                          ##
##                        All Rights Reserved.                           ##
##                                                                       ##
##  Permission is hereby granted, free of charge, to use and distribute  ##
##  this software and its documentation without restriction, including   ##
##  without limitation the rights to use, copy, modify, merge, publish,  ##
##  distribute, sublicense, and/or sell copies of this work, and to      ##
##  permit persons to whom this work is furnished to do so, subject to   ##
##  the following conditions:                                            ##
##   1. The code must retain the above copyright notice, this list of    ##
##      conditions and the following disclaimer.                         ##
##   2. Any modifications must be clearly marked as such.                ##
##   3. Original authors' names are not deleted.                         ##
##   4. The authors' names are not used to endorse or promote products   ##
##      derived from this software without specific prior written        ##
##      permission.                                                      ##
##                                                                       ##
##  CARNEGIE MELLON UNIVERSITY AND THE CONTRIBUTORS TO THIS WORK         ##
##  DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING      ##
##  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT   ##
##  SHALL CARNEGIE MELLON UNIVERSITY NOR THE CONTRIBUTORS BE LIABLE      ##
##  FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    ##
##  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN   ##
##  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,          ##
##  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF       ##
##  THIS SOFTWARE.                                                       ##
##                                                                       ##
###########################################################################
##                                                                       ##
##  Build a flite voice from a festvox voice                             ##
##                                                                       ##
##  C files are built into flite/                                        ##
##                                                                       ##
###########################################################################

FIND_STS=$FLITEDIR/bin/find_sts
FESTIVAL=$ESTDIR/../festival/bin/festival

if [ "$PROMPTFILE" = "" ]
then
   if [ $# -ge 2 ]
   then
      export PROMPTFILE=$2
   else
      export PROMPTFILE=etc/txt.done.data
   fi
fi

if [ "$SIODHEAPSIZE" = "" ]
then
   SIODHEAPSIZE=20000000
   export SIODHEAPSIZE
fi
HEAPSIZE=$SIODHEAPSIZE

. ./etc/voice.defs

if [ $# = 0 ]
then
   if [ "$FV_TYPE" = "clunits" ]
   then
      $0 lpc
      $0 sts
      $0 mcep
      $0 clunits
   elif [ "$FV_TYPE" = "ldom" ]
   then
      $0 lpc
      $0 sts
      $0 mcep
      $0 clunits
   elif [ "$FV_TYPE" = "diphone" ]
   then
      $0 lpc
      $0 sts etc/txt.done.data g721
      $0 diphone
   elif [ "$FV_TYPE" = "cg" ]
   then
      $0 cg
   else
      echo $0 unsupported voicetype $FV_TYPE
   fi   
   exit 0
fi

# Build lpc coefficients
if [ "$1" = "lpc" ]
then
   echo "Finding LPC coefficients"

   ./bin/make_lpc $PROMPTFILE

   echo "Finding LPC min, max and range"
   # make lpc.params file
   cat $PROMPTFILE |
   awk '{print $2}' |
   while read i
   do
      $ESTDIR/bin/ch_track -otype est_ascii lpc/$i.lpc | 
      sed '1,/EST_Header_End/d'
   done |
   awk 'BEGIN {min=0; max=0;}
        {for (i=4; i<=NF; i++)
        {
            if ($i < min) min = $i;
            if ($i > max) max = $i;
        }
     } 
     END {printf("LPC_MIN=%f\n",min);
          printf("LPC_MAX=%f\n",max);
          printf("LPC_RANGE=%f\n",max-min);
         }' >lpc/lpc.params

   exit 0
fi

# build sts files
if [ "$1" = "sts" ]
then
   echo "Finding STS files"
   if [ ! -d sts ]
   then
      mkdir sts
   fi
   . ./lpc/lpc.params

   CODEC=ulaw
   if [ "$#" = "3" ]
   then
      CODEC=$3
   fi

   cat $PROMPTFILE |
   awk '{print $2}' |
   while read i
   do
      fname=$i
      echo $fname STS
      if [ $CODEC = "g721vuv" -o $CODEC = "vuv" ]      
      then
         $ESTDIR/bin/ch_track -itype ascii -otype est_binary -s 0.005 -o v.v v/$fname.v
         $FIND_STS -lpcmin $LPC_MIN -lpcrange $LPC_RANGE -lpc lpc/$fname.lpc -wave wav/$fname.wav -o sts/$fname.sts -codec $CODEC -vuv v.v
      else
         $FIND_STS -lpcmin $LPC_MIN -lpcrange $LPC_RANGE -lpc lpc/$fname.lpc -wave wav/$fname.wav -o sts/$fname.sts -codec $CODEC
      fi
   done
   echo $CODEC >flite/codec

   exit 0
fi

if [ "$1" = "voxdata" ]
then  # for clunits external data
    echo converting ${FV_VOICENAME}_ressize
    grep "^ " flite/${FV_VOICENAME}_ressize.c |
    tr -d "," |
    awk '{for (i=1; i<=NF; i++) 
          {
             if ($i > 255)
                print 255;
             else
                print $i;}}' |
    $FLITEDIR/tools/a2byte.pl >flite/${FV_VOICENAME}_ressize.raw

    echo converting ${FV_VOICENAME}_lpc
    grep "^ " flite/${FV_VOICENAME}_lpcall.c |
    tr -d "," | sed 's/^ //;s/  */\n/g' |
    $FLITEDIR/tools/a2short.pl >flite/${FV_VOICENAME}_lpc.raw

    echo converting ${FV_VOICENAME}_mcep
    grep "^ " flite/${FV_VOICENAME}_mcepall.c |
    tr -d "," | sed 's/^ //;s/  */\n/g' | 
    $FLITEDIR/tools/a2short.pl >flite/${FV_VOICENAME}_mcep.raw

    echo converting ${FV_VOICENAME}_residual
    grep "^ " flite/${FV_VOICENAME}_resall.c |
    tr -d "," | sed 's/^ //;s/  */\n/g' |
    $FLITEDIR/tools/a2byte.pl >flite/${FV_VOICENAME}_res.raw

    echo converting ${FV_VOICENAME}_residx
    grep "^ " flite/${FV_VOICENAME}_residx.c |
    tr -d "," | sed 's/^  *//;s/  */\n/g' |
    $FLITEDIR/tools/a2int.pl >flite/${FV_VOICENAME}_residx.raw

    wc flite/${FV_VOICENAME}_residx.raw \
       flite/${FV_VOICENAME}_lpc.raw \
       flite/${FV_VOICENAME}_mcep.raw \
       flite/${FV_VOICENAME}_res.raw \
       flite/${FV_VOICENAME}_ressize.raw

    printf "CMUFLITE\0"             >flite/${FV_VOICENAME}.voxdata
    printf "%54s\0" ${FV_VOICENAME} >>flite/${FV_VOICENAME}.voxdata
    wc flite/${FV_VOICENAME}_residx.raw \
       flite/${FV_VOICENAME}_lpc.raw \
       flite/${FV_VOICENAME}_mcep.raw \
       flite/${FV_VOICENAME}_res.raw \
       flite/${FV_VOICENAME}_ressize.raw |
    awk '{if ($4 != "total") print $3}' |
    $FLITEDIR/tools/a2int.pl >>flite/${FV_VOICENAME}.voxdata
    # Order goes from 4 bytes, 2 bytes, 1 byte info to ensure alignment
    cat flite/${FV_VOICENAME}_residx.raw >>flite/${FV_VOICENAME}.voxdata
    cat flite/${FV_VOICENAME}_lpc.raw >>flite/${FV_VOICENAME}.voxdata
    cat flite/${FV_VOICENAME}_mcep.raw >>flite/${FV_VOICENAME}.voxdata
    cat flite/${FV_VOICENAME}_res.raw >>flite/${FV_VOICENAME}.voxdata
    cat flite/${FV_VOICENAME}_ressize.raw >>flite/${FV_VOICENAME}.voxdata

   exit 0
fi

# build mcep (params)
if [ "$1" = "mcep" ]
then
   echo "Finding MCEP min max and range"
   cat $PROMPTFILE |
   awk '{print $2}' |
   while read i
   do
      $ESTDIR/bin/ch_track -otype est_ascii mcep/$i.mcep |
      sed '1,/EST_Header_End/d'
   done |
   awk 'BEGIN {min=0; max=0;}
        {for (i=4; i<=NF; i++)
        {
            if ($i < min) min = $i;
            if ($i > max) max = $i;
        }
     } 
     END {printf("(set! mcep_min %f)\n",min);
          printf("(set! mcep_max %f)\n",max);
          printf("(set! mcep_range %f)\n",max-min);
         }' >mcep/mcep.params.scm
   exit 0
fi

if [ "$1" = "cg_spam" ]
then

   #Dump phrase tree
   $FESTIVAL --heap $HEAPSIZE -b \
          $FLITEDIR/tools/make_cg.scm \
          $FLITEDIR/tools/make_cart.scm \
          $FLITEDIR/tools/make_vallist.scm \
	  festvox/${FV_VOICENAME}_cg.scm \
         '(begin
	   (voice_'$FV_VOICENAME'_cg)
	   (if cg:spamf0
           (begin
	   (format t "cg_convert: Dumping spamf0 models\n")
	   (set! current_node -1)
	   (set! feat_nums nil)
	   (set! val_table nil)
	   (set! ptree (car (load "festival/trees/phrase.tree" t)))
	   (set! ofd (fopen (format nil "flite/%s_spamf0_phrase.c" "'$FV_VOICENAME'") "w"))
	   (set! ofdh (fopen (format nil "flite/%s_spamf0_phrase.h" "'$FV_VOICENAME'") "w"))
   	   (format ofd "/*****************************************************/\n")
   	   (format ofd "/**  Autogenerated %s phrase carts    */\n" "'$FV_VOICENAME'")
	   (format ofd "/*****************************************************/\n")
	   (format ofd "\n")
	   (format ofd "#include \"cst_string.h\"\n")
	   (format ofd "#include \"cst_cart.h\"\n")
	   (format ofd "#include \"%s_spamf0_phrase.h\"\n" "'$FV_VOICENAME'")
	   (do_carttoC ofd ofdh (format nil "%s_spamf0_phrase" "'$FV_VOICENAME'") ptree)
	   (fclose ofd)
	   (fclose ofdh)
	)))' 
  
  #Dump accent tree
   $FESTIVAL --heap $HEAPSIZE -b \
          $FLITEDIR/tools/make_cg.scm \
          $FLITEDIR/tools/make_cart.scm \
          $FLITEDIR/tools/make_vallist.scm \
	  festvox/${FV_VOICENAME}_cg.scm \
         '(begin
	   (voice_'$FV_VOICENAME'_cg)
	   (if cg:spamf0
           (begin
	   (set! current_node -1)
	   (set! feat_nums nil)
	   (set! val_table nil)
	   (set! acctree (car (load "festival/trees/acc.tree" t)))
	   (set! ofd (fopen (format nil "flite/%s_spamf0_accent.c" "'$FV_VOICENAME'") "w"))
	   (set! ofdh (fopen (format nil "flite/%s_spamf0_accent.h" "'$FV_VOICENAME'") "w"))
   	   (format ofd "/*****************************************************/\n")
   	   (format ofd "/**  Autogenerated %s spam accent params  */\n" "'$FV_VOICENAME'")
	   (format ofd "/*****************************************************/\n")
	   (format ofd "\n")
	   (format ofd "#include \"cst_string.h\"\n")
	   (format ofd "#include \"cst_cart.h\"\n")
	   (format ofd "#include \"%s_spamf0_accent.h\"\n" "'$FV_VOICENAME'")
         (set! old_carttoC_extract_answer carttoC_extract_answer)
         (set! carttoC_extract_answer carttoC_extract_spectral_frame)
	   (do_carttoC ofd ofdh (format nil "%s_spamf0_accent" "'$FV_VOICENAME'") acctree)
         (set! carttoC_extract_answer old_carttoC_extract_answer)
	   (fclose ofd)
	   (fclose ofdh)
	)))' 

  #Dump accent params
   $FESTIVAL --heap $HEAPSIZE -b \
          $FLITEDIR/tools/make_cg.scm \
          $FLITEDIR/tools/make_cart.scm \
          $FLITEDIR/tools/make_vallist.scm \
	  festvox/${FV_VOICENAME}_cg.scm \
         '(begin
	   (voice_'$FV_VOICENAME'_cg)
	   (if cg:spamf0
           (begin
	   (set! current_node -1)
	   (set! feat_nums nil)
	   (set! val_table nil)
	   (set! mfd (fopen (format nil "flite/%s_spamf0_accent_params.c" "'$FV_VOICENAME'") "w"))
	   (set! acctrack (track.load "festival/trees/cb.params"))
    	   (format mfd "/*****************************************************/\n")
           (format mfd "/**  Autogenerated Spam accent vectors for %s   */\n" "'$FV_VOICENAME'")
           (format mfd "/*****************************************************/\n")
	   (set! num_channels (track.num_channels acctrack))
	   (set! num_frames (track.num_frames acctrack))
	   (set! i 0)
    	   (while (< i num_frames)
       		(output_accent_frame "'$FV_VOICENAME'" acctrack i mfd)
       	   (set! i (+ 1 i)))
	   (format mfd "\n\n")
	   (format mfd "const float * const %s_spamf0_accent_vectors[] = {\n" "'$FV_VOICENAME'" )
	   (set! i 0)
	   (while (< i num_frames)
	      (format mfd "   %s_spamf0_accent_frame_%d,\n" "'$FV_VOICENAME'" i)
     	      (set! i (+ 1 i)))
	   (format mfd "};\n\n")
	   (fclose mfd)
	  )))'
     $0 cg
   exit 0
fi

if [ "$1" = "cg" ]  # clustergen
then
   # For smaller (and quicker) voices you can build with a reduced order
   # this seems to be ok for values down to 13 
   RORDER=0  
   if [ $# = 2 ]
   then
      RORDER=$2
   fi
   echo cg_convert: finding parameter ranges
   $ESTDIR/bin/ch_track -otype est_ascii festival/trees/${FV_VOICENAME}_mcep*.params |
   sed '1,/EST_Header_End/d' |
   awk 'BEGIN {nc=0;}
        {if (nc == 0) nc = NF;
        if (NF == nc )
        {
           for (i=3; i<=NF; i++)
           {
               if ((NR == 1) || ($i < min[i])) min[i] = $i;
               if ((NR == 1) || ($i > max[i])) max[i] = $i;
           }
           nc = NF;
        }
     } 
     END {for (i=3; i<=nc; i++)
          {
             printf("( %f %f )\n",min[i],max[i]-min[i]);
          }
         }' >festival/trees/${FV_VOICENAME}_min_range.scm
   cat etc/f0.params |
   sed 's/=/ /' |
   head -2 |
   awk '{printf("(set! %s %s)\n",$1,$2)}' >flite/f0_params.scm

   $FESTIVAL --heap $HEAPSIZE -b \
         '(set! cg_reduced_order '$RORDER')' \
          flite/f0_params.scm \
          $FLITEDIR/tools/make_cg.scm \
          $FLITEDIR/tools/make_cart.scm \
          $FLITEDIR/tools/make_vallist.scm \
         '(cg_convert 
                   "'$FV_VOICENAME'"
                   "."
                   "flite/")'

   touch flite/${FV_VOICENAME}_voice_feats.c
   if [ -f etc/voice.feats ]
   then
      ${FLITEDIR}/tools/make_flite_feats etc/voice.feats >flite/${FV_VOICENAME}_voice_feats.c
   fi

   # If Grapheme-based it has its own phoneset
   if [ -f festvox/${FV_VOICENAME}_char_phone_map.scm ]
   then
       echo "flite_feat_set_int(vox->features,\"grapheme\",1);" >>flite/${FV_VOICENAME}_voice_feats.c
       echo cg_convert: converting phoneset table
       echo "CG_GRAPHEME=true" >>flite/paramfiles.mak
      $FESTIVAL -b $FLITEDIR/tools/make_phoneset.scm \
      '(phonesettoC "'${FV_VOICENAME}'" (car (load "festvox/'${FV_VOICENAME}'_phoneset.scm" t)) "pau" "flite")'
   fi

   echo "flite_build cg complete.  You can compile the generated voice by"
   echo "   cd flite; make"

   exit 0
fi

if [ "$1" = "clunits" ]  # clunits or ldom 
then
   echo "Building clunits/ldom index"
   sed '1,/EST_Header_End/d' festival/clunits/$FV_VOICENAME.catalogue |
   awk 'BEGIN {p="CLUNIT_NONE";}
        {if ((NR > 1) && (t != "0_0"))
        {
            n = split(t,bits,"_");
            unit_type = substr(t,1,length(t)-(length(bits[n])+1));
            unit_occur = bits[n];
            if ((t == "0_0") || (f != $2) || ($1 == "0_0"))
               printf("%s-%05d -- ( %s %s %s )\n",unit_type,unit_occur,line,p,"CLUNIT_NONE");
            else
               printf("%s-%05d -- ( %s %s unit_%s )\n",unit_type,unit_occur, line,p,$1);
         }
         line = $0;
         if ((t == "0_0") || (f != $2))
            p = "CLUNIT_NONE";
         else
             p=sprintf("unit_%s",t);
         t=$1;
         f=$2;
        } 
        END { if (t != "0_0")
              {
                n = split(t,bits,"_");
                unit_type = substr(t,1,length(t)-(length(bits[n])+1));
                unit_occur = bits[n];
                printf("%s-%05d -- ( %s %s %s )\n", unit_type,unit_occur,line,p,"CLUNIT_NONE");
              } }' |
        cat >festival/clunits/$FV_VOICENAME.scm
        cat festival/clunits/$FV_VOICENAME.scm |
        $FLITEDIR/bin/flite_sort |
        sed 's/^.* -- //'  >festival/clunits/$FV_VOICENAME.unitordered.scm
        cat festival/clunits/$FV_VOICENAME.scm |
        sed 's/^.* -- //'  >festival/clunits/$FV_VOICENAME.fileordered.scm

   rm -f flite/$FV_VOICENAME"_lpc"*
   rm -f flite/$FV_VOICENAME"_mcep"*
   rm -f flite/$FV_VOICENAME"_res"*
   $FESTIVAL --heap $HEAPSIZE -b $FLITEDIR/tools/make_clunits.scm \
          $FLITEDIR/tools/make_cart.scm \
          $FLITEDIR/tools/make_vallist.scm \
          mcep/mcep.params.scm \
         '(clunits_convert "'$FV_VOICENAME'"
                   "festival/clunits/'$FV_VOICENAME'.fileordered.scm"
                   "festival/clunits/'$FV_VOICENAME'.unitordered.scm"
                   "festival/trees/'$FV_VOICENAME'.tree"
                   "."
                   "flite/")'

   echo "flite_build complete.  You can compile the generated voice by"
   echo "   cd flite; make"
   exit 0
fi

if [ "$1" = "diphone" ]
then
   echo "Building diphone index"
   # Not a good way to do this, to take the most recent
   idxfile=`ls -t dic/*.est | head -1`
   echo "Using "$idxfile
   sed '1,/EST_Header_End/d' $idxfile |
   awk '{printf("%s ( %s )\n",$1,$0)}' |
   $FLITEDIR/bin/flite_sort |
   sed 's/^.* (/(/' >dic/diphidx.scm
   $FESTIVAL --heap $HEAPSIZE -b $FLITEDIR/tools/make_didb2.scm \
     '(diphtoC "dic/diphidx.scm" "'$FV_VOICENAME'" "sts" "flite")'

   echo "flite_build complete.  You can compile the generated voice by"
   echo "   cd flite; make"
   exit 0
fi

# build index file
if [ "$1" = "idx" ]
then
   HEAPSIZE=100000000
   if [ "$FV_TYPE" != "diphone" ]  # clunits or ldom 
   then
      echo "Building clunits/ldom index"
      sed '1,/EST_Header_End/d' festival/clunits/$FV_VOICENAME.catalogue |
      awk 'BEGIN {p="CLUNIT_NONE";}
           {if ((NR > 1) && (t != "0_0"))
           {
               n = split(t,bits,"_");
               unit_type = substr(t,1,length(t)-(length(bits[n])+1));
               unit_occur = bits[n];
               if ((t == "0_0") || (f != $2) || ($1 == "0_0"))
                  printf("%s_%05d -- ( %s %s %s )\n",unit_type,unit_occur,line,p,"CLUNIT_NONE");
               else
                  printf("%s_%05d -- ( %s %s unit_%s )\n",unit_type,unit_occur, line,p,$1);
            }
            line = $0;
   	    if ((t == "0_0") || (f != $2))
                p = "CLUNIT_NONE";
            else
                p=sprintf("unit_%s",t);
            t=$1;
            f=$2;
           } 
           END { if (t != "0_0")
                 {
                   n = split(t,bits,"_");
                   unit_type = substr(t,1,length(t)-(length(bits[n])+1));
                   unit_occur = bits[n];
                   printf("%s_%05d -- ( %s %s %s )\n", unit_type,unit_occur,line,p,"CLUNIT_NONE");
                 } }' |
           cat >festival/clunits/$FV_VOICENAME.scm
           cat festival/clunits/$FV_VOICENAME.scm |
	   $FLITEDIR/bin/flite_sort |
           sed 's/^.* -- //'  >festival/clunits/$FV_VOICENAME.unitordered.scm
           cat festival/clunits/$FV_VOICENAME.scm |
           sed 's/^.* -- //'  >festival/clunits/$FV_VOICENAME.fileordered.scm

      rm -f flite/$FV_VOICENAME"_lpc"*
      rm -f flite/$FV_VOICENAME"_mcep"*
      rm -f flite/$FV_VOICENAME"_res"*
      $FESTIVAL --heap $HEAPSIZE -b $FLITEDIR/tools/make_clunits.scm \
             $FLITEDIR/tools/make_cart.scm \
             $FLITEDIR/tools/make_vallist.scm \
             mcep/mcep.params.scm \
            '(clunits_convert "'$FV_VOICENAME'"
                      "festival/clunits/'$FV_VOICENAME'.fileordered.scm"
                      "festival/clunits/'$FV_VOICENAME'.unitordered.scm"
                      "festival/trees/'$FV_VOICENAME'.tree"
                      "."
                      "flite/")'
   fi
   if [ "$FV_TYPE" = "diphone" ]
   then
      echo "Building diphone index"
      # Not a good way to do this, to take the most recent
      idxfile=`ls -t dic/*.est | head -1`
      echo "Using "$idxfile
      sed '1,/EST_Header_End/d' $idxfile |
      awk '{printf("%s ( %s )\n",$1,$0)}' |
      $FLITEDIR/bin/flite_sort |
      sed 's/^.* (/(/' >dic/diphidx.scm

      $FESTIVAL --heap $HEAPSIZE -b $FLITEDIR/tools/make_didb.scm \
      '(diphtoC "dic/diphidx.scm" "'$FV_VOICENAME'" "sts" "flite")'

   fi

   echo "flite_build complete.  You can compile the generated voice by"
   echo "   cd flite; make"
   exit 0
fi

echo build_flite unknown arguments $*
exit 1

